package com.lyf.scm.plan.domain.service.impl;

import com.github.pagehelper.PageInfo;
import com.lyf.scm.common.constants.CommonConstants;
import com.lyf.scm.common.enums.OperationDataEnum;
import com.lyf.scm.common.enums.ResponseMsg;
import com.lyf.scm.common.enums.SalesPlanDirectlyExecuteEnum;
import com.lyf.scm.common.model.BaseSearchDTO;
import com.lyf.scm.common.model.ExcelExportDataDTO;
import com.lyf.scm.common.model.SalesPlanSelectDTO;
import com.lyf.scm.plan.api.dto.DirectlyExecuteDTO;
import com.lyf.scm.plan.api.dto.DirectlyExecuteInfoDTO;
import com.lyf.scm.plan.api.dto.RoleAuthorityAreaDTO;
import com.lyf.scm.plan.domain.remote.basedata.dto.StoreDTO;
import com.lyf.scm.plan.domain.remote.basedata.facade.BaseDataFacade;
import com.lyf.scm.plan.domain.remote.bigdata.facade.BlOlapFacade;
import com.lyf.scm.plan.domain.remote.item.facade.ItemCoreFacade;
import com.lyf.scm.plan.domain.repository.*;
import com.lyf.scm.plan.domain.service.DirectlyExecuteService;
import com.lyf.scm.plan.insfrastructure.db.dataobject.bigdata.DirectlyExecuteInDO;
import com.lyf.scm.plan.insfrastructure.db.dataobject.bigdata.DirectlyExecuteUpDO;
import com.rome.arch.core.exception.RomeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import static com.lyf.scm.common.util.BatchHandlerUtil.splitTo;
import static com.lyf.scm.plan.domain.util.BigDataCacheUtil.*;

/**
 * description:
 * 直营预测计划--执行
 *
 * @author: haodong
 * @data: 2020/8/5 13:31
 */
@Slf4j
@Service
public class DirectlyExecuteServiceImpl implements DirectlyExecuteService {

    @Resource
    private DirectlyExecuteRepository directlyExecuteRepository;
    @Resource
    private RoleAuthorityAreaRepository roleAuthorityAreaRepository;
    @Resource
    private ItemCoreFacade itemCoreFacade;
    @Resource
    private BaseDataFacade baseDataFacade;
    @Resource
    private OperationLogRecordRepository operationLogRecordRepository;
    @Resource
    private SalesPlanEsRepository esRepository;
    @Resource
    private BlOlapFacade blOlapFacade;
    @Resource
    private BaseQueryRepository baseQueryRepository;
    /**
     * 按条件查询直营预测计划
     *
     * @param directly
     * @return
     */
    @Override
    public PageInfo<DirectlyExecuteInfoDTO> listBySelective(DirectlyExecuteDTO directly) {
        PageInfo<DirectlyExecuteInfoDTO> resultPageInfo = new PageInfo<>();
        //获取当前用户权限范围
        List<RoleAuthorityAreaDTO> roleAreaList = roleAuthorityAreaRepository.listByUserId(directly.getCreator());
        baseQueryRepository.checkPermissionArea(directly);
        // List<ESReturnDTO> resultList = esRepository.listPageByGroup(directly, BigDataEsConstants.INDEX_NAME_PLAN_EXECUTE,
        //         Arrays.asList(SalesPlanDirectlyExecuteEnum.FORECAST_SALES_AMOUNT.getColumns()));
        // if (CollectionUtils.isEmpty(resultList)){
        //     return resultPageInfo;
        // }
        // List<DirectlyExecuteInfoDTO> directlyReturn = parsingData(directly,resultList);
        return null;
    }



    /**
     * 查询全渠道直营预测计划
     *
     * @param directlyExecute
     * @return
     */
    @Override
    public List<DirectlyExecuteInfoDTO> listAllByDays(DirectlyExecuteDTO directlyExecute) {
        return null;
    }
/**
     * 导出
     *
     * @param directlyExecute
     * @param type
     * @return
     */
    @Override
    public ExcelExportDataDTO exportExcel(DirectlyExecuteDTO directlyExecute, Integer type) {
        return null;
    }

    /**
     * 查询数据为空时的处理
     *
     * @param directlyExecute
     * @return
     */
    private List<DirectlyExecuteInfoDTO> getNullTargetList(DirectlyExecuteDTO directlyExecute) {
        List<Date> dateList = directlyExecute.getDateList();
        List<DirectlyExecuteInfoDTO> salesPlanDirectlyList = new ArrayList<>();
        List<BigDecimal> historicalSalesAmount = new ArrayList<>();
        List<BigDecimal> forecastSalesAmount = new ArrayList<>();
        List<BigDecimal> forecastDifferenceList = new ArrayList<>();
        List<BigDecimal> forecastDifferenceRateList = new ArrayList<>();
        List<BigDecimal> actualDifferenceList = new ArrayList<>();
        List<BigDecimal> actualDifferenceRateList = new ArrayList<>();
        DirectlyExecuteInfoDTO salesPlanDirectlyInfo = new DirectlyExecuteInfoDTO();
        dateList.forEach(date -> {
            historicalSalesAmount.add(BigDecimal.ZERO);
            forecastSalesAmount.add(BigDecimal.ZERO);
            forecastDifferenceList.add(BigDecimal.ZERO);
            forecastDifferenceRateList.add(BigDecimal.ZERO);
            actualDifferenceList.add(BigDecimal.ZERO);
            actualDifferenceRateList.add(BigDecimal.ZERO);
        });
        salesPlanDirectlyInfo.setHistoricalSalesAmountLabel(SalesPlanDirectlyExecuteEnum.HISTORICAL_SALES_AMOUNT.getDesc());
        salesPlanDirectlyInfo.setHistoricalSalesAmount(historicalSalesAmount);
        salesPlanDirectlyInfo.setForecastSalesAmountLabel(SalesPlanDirectlyExecuteEnum.FORECAST_SALES_AMOUNT.getDesc());
        salesPlanDirectlyInfo.setForecastSalesAmount(forecastSalesAmount);
        salesPlanDirectlyInfo.setForecastDifferenceLabel(SalesPlanDirectlyExecuteEnum.FORECAST_DIFFERENCE.getDesc());
        salesPlanDirectlyInfo.setForecastDifference(forecastDifferenceList);
        salesPlanDirectlyInfo.setForecastDifferenceRateLabel(SalesPlanDirectlyExecuteEnum.FORECAST_DIFFERENCE_RATE.getDesc());
        salesPlanDirectlyInfo.setForecastDifferenceRate(forecastDifferenceRateList);
        salesPlanDirectlyInfo.setActualDifferenceLabel(SalesPlanDirectlyExecuteEnum.ACTUAL_DIFFERENCE.getDesc());
        salesPlanDirectlyInfo.setActualDifference(actualDifferenceList);
        salesPlanDirectlyInfo.setActualDifferenceRateLabel(SalesPlanDirectlyExecuteEnum.ACTUAL_DIFFERENCE_RATE.getDesc());
        salesPlanDirectlyInfo.setActualDifferenceRate(actualDifferenceRateList);
        salesPlanDirectlyList.add(salesPlanDirectlyInfo);
        return salesPlanDirectlyList;
    }


    /**
     * 获取预测差值比例：（昨天预测量-今天预测量）/今天预测量；预测差值：昨天预测量-今天预测量
     *
     * @param directlyExeInfo
     * @param forecastSalesAmountList
     * @param lastForecastAmount
     * @return
     */
    private void calculationRatio(DirectlyExecuteInfoDTO directlyExeInfo, List<BigDecimal> forecastSalesAmountList, BigDecimal lastForecastAmount) {
        //第一天数据拿获取的前一天数据计算差值
        List<BigDecimal> forecastDifferenceList = new ArrayList<>();
        List<BigDecimal> forecastDifferenceRateList = new ArrayList<>();
        if (lastForecastAmount.compareTo(BigDecimal.ZERO) != 0) {
            BigDecimal forecastDifference = lastForecastAmount.subtract(forecastSalesAmountList.get(0));
            forecastDifferenceList.add(forecastDifference);
            BigDecimal firstDay = lastForecastAmount.compareTo(BigDecimal.ZERO) == 0 ?
                    BigDecimal.ZERO : forecastDifference.divide(lastForecastAmount, 7, BigDecimal.ROUND_HALF_UP);
            forecastDifferenceRateList.add(firstDay);
        } else {
            forecastDifferenceList.add(null);
            forecastDifferenceRateList.add(null);
        }
        int beginIdx = 1;
        int lastIdx = forecastSalesAmountList.size() - 1;
        for (int i = 0; i < lastIdx; i++) {
            BigDecimal yesterdayQty = forecastSalesAmountList.get(i);
            BigDecimal todayQty = forecastSalesAmountList.get(beginIdx);
            BigDecimal forecastDifference = yesterdayQty.subtract(todayQty);
            forecastDifferenceList.add(forecastDifference);
            BigDecimal forecastDifferenceRate = todayQty.compareTo(BigDecimal.ZERO) == 0 ?
                    BigDecimal.ZERO : forecastDifference.divide(todayQty, 7, BigDecimal.ROUND_HALF_UP);
            forecastDifferenceRateList.add(forecastDifferenceRate);
            beginIdx++;
        }
        // 预测差值
        directlyExeInfo.setForecastDifferenceLabel(SalesPlanDirectlyExecuteEnum.FORECAST_DIFFERENCE.getDesc());
        directlyExeInfo.setForecastDifference(forecastDifferenceList);
        // 预测差值比例
        directlyExeInfo.setForecastDifferenceRateLabel(SalesPlanDirectlyExecuteEnum.FORECAST_DIFFERENCE_RATE.getDesc());
        directlyExeInfo.setForecastDifferenceRate(forecastDifferenceRateList);
    }

    /**
     * 获取实际差值比例：（昨天实际销量-今天实际销量）/今天实际销量;实际差值：昨天实际销量-今天实际销量
     *
     * @param directlyExeInfo
     * @param lastHistorySalesAmount
     * @return
     */
    private void calculationDifference(DirectlyExecuteInfoDTO directlyExeInfo, List<BigDecimal> historicalSalesAmount, BigDecimal lastHistorySalesAmount) {
        List<BigDecimal> actualDifferenceList = new ArrayList<>();
        List<BigDecimal> actualDifferenceRateList = new ArrayList<>();
        if (lastHistorySalesAmount.compareTo(BigDecimal.ZERO) != 0) {
            BigDecimal forecastDifference = lastHistorySalesAmount.subtract(historicalSalesAmount.get(0));
            actualDifferenceList.add(forecastDifference);
            BigDecimal firstDay = lastHistorySalesAmount.compareTo(BigDecimal.ZERO) == 0 ?
                    BigDecimal.ZERO : forecastDifference.divide(lastHistorySalesAmount, 7, BigDecimal.ROUND_HALF_UP);
            actualDifferenceRateList.add(firstDay);
        } else {
            actualDifferenceList.add(null);
            actualDifferenceRateList.add(null);
        }
        int beginIdx = 1;
        int lastIdx = historicalSalesAmount.size() - 1;
        for (int i = 0; i < lastIdx; i++) {
            BigDecimal yesterdayQty = historicalSalesAmount.get(i);
            BigDecimal todayQty = historicalSalesAmount.get(beginIdx);
            BigDecimal forecastDifference = yesterdayQty.subtract(todayQty);
            actualDifferenceList.add(forecastDifference);
            BigDecimal forecastDifferenceRate = todayQty.compareTo(BigDecimal.ZERO) == 0 ?
                    BigDecimal.ZERO : forecastDifference.divide(todayQty, 7, BigDecimal.ROUND_HALF_UP);
            actualDifferenceRateList.add(forecastDifferenceRate);
            beginIdx++;
        }
        // 实际差值
        directlyExeInfo.setActualDifferenceLabel(SalesPlanDirectlyExecuteEnum.ACTUAL_DIFFERENCE.getDesc());
        directlyExeInfo.setActualDifference(actualDifferenceList);
        // 实际差值比例
        directlyExeInfo.setActualDifferenceRateLabel(SalesPlanDirectlyExecuteEnum.ACTUAL_DIFFERENCE_RATE.getDesc());
        directlyExeInfo.setActualDifferenceRate(actualDifferenceRateList);
    }



    /**
     * 校验角色区域信息
     *
     * @param baseSearchDTO
     * @return void
     **/
    private void getAuthorityArea(BaseSearchDTO baseSearchDTO) {
        List<RoleAuthorityAreaDTO> roleAuthorityArea = roleAuthorityAreaRepository.listByUserId(baseSearchDTO.getCreator());
        List<RoleAuthorityAreaDTO> roleAuthorityAreaList = roleAuthorityArea.stream().filter(item -> StringUtils.isEmpty(item.getChannelCode())).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(roleAuthorityAreaList)) {
            throw new RomeException(ResponseMsg.FAIL_2014.getCode(), ResponseMsg.FAIL_2014.getMsg());
        }
        //获取该角色的区域信息
        List<SalesPlanSelectDTO> salesPlanSelectS = roleAuthorityAreaList.stream().map(item -> {
            SalesPlanSelectDTO salesPlanSelectDTO = new SalesPlanSelectDTO();
            if (StringUtils.isNotEmpty(item.getCountyCode())) {
                salesPlanSelectDTO.setLevel(CommonConstants.AREA_LEVEL_THREE);
                salesPlanSelectDTO.setCode(item.getCountyCode());
                return salesPlanSelectDTO;
            }
            if (StringUtils.isNotEmpty(item.getCityCode())) {
                salesPlanSelectDTO.setLevel(CommonConstants.AREA_LEVEL_TWO);
                salesPlanSelectDTO.setCode(item.getCityCode());
                return salesPlanSelectDTO;
            }
            if (StringUtils.isNotEmpty(item.getProvinceCode())) {
                salesPlanSelectDTO.setLevel(CommonConstants.AREA_LEVEL_ONE);
                salesPlanSelectDTO.setCode(item.getProvinceCode());
                return salesPlanSelectDTO;
            }
            throw new RomeException(ResponseMsg.FAIL_2014.getCode(), ResponseMsg.FAIL_2014.getMsg());
        }).collect(Collectors.toList());
        //将其统一到最低层级
        List<SalesPlanSelectDTO> roleAreas = baseDataFacade.getSubAreaCode(salesPlanSelectS);
        //如果有查询条件
        if (!CollectionUtils.isEmpty(baseSearchDTO.getAreaCodeList())) {
            //将查询条件汇总到最低层级
            List<SalesPlanSelectDTO> searchAreas = baseDataFacade.getSubAreaCode(baseSearchDTO.getAreaCodeList());
            List<SalesPlanSelectDTO> result = null;
            Integer searchLevel = searchAreas.get(0).getLevel();
            Integer roleLevel = roleAreas.get(0).getLevel();
            if (searchLevel > roleLevel) {
                result = this.recursionLevel(roleAreas, searchLevel);
                if (!result.containsAll(searchAreas)) {
                    throw new RomeException(ResponseMsg.FAIL_1906.getCode(), ResponseMsg.FAIL_1906.getMsg());
                }
            }
            if (searchLevel.equals(roleLevel)) {
                if (!roleAreas.containsAll(searchAreas)) {
                    throw new RomeException(ResponseMsg.FAIL_1906.getCode(), ResponseMsg.FAIL_1906.getMsg());
                }
            }
            if (searchLevel < roleLevel) {
                result = this.recursionLevel(searchAreas, roleLevel);
                if (!roleAreas.containsAll(result)) {
                    throw new RomeException(ResponseMsg.FAIL_1906.getCode(), ResponseMsg.FAIL_1906.getMsg());
                }
            }
            setAreaSearch(searchAreas, baseSearchDTO);
        } else {
            setAreaSearch(roleAreas, baseSearchDTO);
        }
    }

    private void setAreaSearch(List<SalesPlanSelectDTO> areas, BaseSearchDTO baseSearchDTO) {
        Integer level = areas.get(0).getLevel();
        if (CommonConstants.AREA_LEVEL_ONE.equals(level)) {
            baseSearchDTO.setProvinceCodeList(areas.stream().map(SalesPlanSelectDTO::getCode).distinct().collect(Collectors.toList()));
        }
        if (CommonConstants.AREA_LEVEL_TWO.equals(level)) {
            baseSearchDTO.setCityCodeList(areas.stream().map(SalesPlanSelectDTO::getCode).distinct().collect(Collectors.toList()));
        }
        if (CommonConstants.AREA_LEVEL_THREE.equals(level)) {
            baseSearchDTO.setCountyCodeList(areas.stream().map(SalesPlanSelectDTO::getCode).distinct().collect(Collectors.toList()));
        }
        if (CommonConstants.AREA_LEVEL_FOUR.equals(level)) {
            baseSearchDTO.setStoreCodeList(areas.stream().map(SalesPlanSelectDTO::getCode).distinct().collect(Collectors.toList()));
        }
    }

    private List<SalesPlanSelectDTO> recursionLevel(List<SalesPlanSelectDTO> searchAreas, Integer level) {
        List<String> codeList = searchAreas.stream().map(SalesPlanSelectDTO::getCode).collect(Collectors.toList());
        if (level.equals(CommonConstants.AREA_LEVEL_FOUR)) {
            List<StoreDTO> storeList = new ArrayList<>();
            if (searchAreas.get(0).getLevel().equals(CommonConstants.AREA_LEVEL_ONE)) {
                storeList = baseDataFacade.ListStoreByArea(codeList, CommonConstants.AREA_LEVEL_ONE, 1, 5000);
            }
            if (searchAreas.get(0).getLevel().equals(CommonConstants.AREA_LEVEL_TWO)) {
                storeList = baseDataFacade.ListStoreByArea(codeList, CommonConstants.AREA_LEVEL_TWO, 1, 5000);
            }
            if (searchAreas.get(0).getLevel().equals(CommonConstants.AREA_LEVEL_THREE)) {
                storeList = baseDataFacade.ListStoreByArea(codeList, CommonConstants.AREA_LEVEL_THREE, 1, 5000);
            }
            List<SalesPlanSelectDTO> areaList = storeList.stream().map(item -> {
                SalesPlanSelectDTO salesPlanSelectDTO = new SalesPlanSelectDTO();
                salesPlanSelectDTO.setCode(item.getCode());
                salesPlanSelectDTO.setLevel(level);
                return salesPlanSelectDTO;
            }).collect(Collectors.toList());
            return areaList;
        } else {
            if (searchAreas.get(0).getLevel() < level) {
                List<SalesPlanSelectDTO> areas = baseDataFacade.batchSearchByParentCode(codeList).stream().map(item -> {
                    SalesPlanSelectDTO salesPlanSelectDTO = new SalesPlanSelectDTO();
                    salesPlanSelectDTO.setCode(item.getCode());
                    salesPlanSelectDTO.setLevel(item.getLevel());
                    return salesPlanSelectDTO;
                }).collect(Collectors.toList());
                return this.recursionLevel(areas, level);
            }
        }
        return searchAreas;
    }



    /**
     * 处理大数据任务，先删除后新增
     *
     * @param directlyExecuteInDOs
     * @return void
     **/
    @Transactional(rollbackFor = Exception.class)
    public void handleBigData(List<DirectlyExecuteInDO> directlyExecuteInDOs, String storeCode) {
        //删除当前日期之后的所有数据
        directlyExecuteRepository.batchDeleteByStartDate(storeCode, dayStartDate);
        if (!CollectionUtils.isEmpty(directlyExecuteInDOs)) {
            List<List<DirectlyExecuteInDO>> lists = splitTo(directlyExecuteInDOs, 5000);
            for (List<DirectlyExecuteInDO> list : lists) {
                directlyExecuteRepository.batchInsert(list);
            }
        }
    }

    /**
     * 补救处理大数据任务
     *
     * @param directlyExecuteInDOs
     * @param storeCode
     * @return void
     **/
    @Transactional(rollbackFor = Exception.class)
    public void handleRemedialBigData(List<DirectlyExecuteInDO> directlyExecuteInDOs, String storeCode) {
        //删除当前日期之后的所有数据
        directlyExecuteRepository.batchDeleteByStartDate(storeCode, dayStartDate);
        if (!CollectionUtils.isEmpty(directlyExecuteInDOs)) {
            List<List<DirectlyExecuteInDO>> lists = splitTo(directlyExecuteInDOs, 5000);
            for (List<DirectlyExecuteInDO> list : lists) {
                directlyExecuteRepository.batchInsert(list);
            }
        }
        //删除错误日志信息，这里做并发控制，如果未正确删除，进行回滚，由service层catch不记录该日志
        operationLogRecordRepository.deleteBigDataErrorLog(OperationDataEnum.DIRECTLY_EXECUTE.getTableType(), startTime, endTime, storeCode);
    }

    @Override
    public void delete(String opSign) {
        directlyExecuteRepository.delete(opSign);
    }

    /**
     * 更新方案处理大数据
     *
     * @param directlyExecuteInDOs
     * @param directlyExecuteUpDOs
     * @return void
     **/
    @Transactional(rollbackFor = Exception.class)
    public void handleUpBigData(List<DirectlyExecuteInDO> directlyExecuteInDOs, List<DirectlyExecuteUpDO> directlyExecuteUpDOs, List<Long> ids) {
//先删除
        if (!CollectionUtils.isEmpty(ids)) {
            directlyExecuteRepository.rmByIds(ids);
        }
        //更新
        if (!CollectionUtils.isEmpty(directlyExecuteUpDOs)) {
            List<List<DirectlyExecuteUpDO>> lists = splitTo(directlyExecuteUpDOs, 2000);
            for (List<DirectlyExecuteUpDO> list : lists) {
                directlyExecuteRepository.upByIds(list);
            }
        }
        if(!CollectionUtils.isEmpty(directlyExecuteInDOs)){
            List<List<DirectlyExecuteInDO>> lists = splitTo(directlyExecuteInDOs, 5000);
            for (List<DirectlyExecuteInDO> list : lists) {
                directlyExecuteRepository.batchInsert(list);
            }
        }
    }
}
